package com.gitee.wsl.reflect.func.call.binding

import com.gitee.wsl.reflect.func.call.exception.IllegalRawValueException
import com.gitee.wsl.reflect.func.value.OutputValue
import com.gitee.wsl.reflect.func.value.Value


/**
 * Suffix that marks a lambda parameter as optional.
 * @see ValueFactory.lambda
 */
private const val LAMBDA_OPTIONAL_PARAMETER_SUFFIX = '?'

/**
 * Prefix that forces a generic expression to be parsed as a lambda block.
 * @see ValueFactory.expression
 */
private const val EXPRESSION_FORCE_LAMBDA_PREFIX = "@lambda "

/**
 * Factory of [Value] wrappers from raw data.
 * @see com.quarkdown.core.function.reflect.FromDynamicType
 * @see com.quarkdown.core.function.reflect.DynamicValueConverter.convertTo
 */
object ValueFactory {
    /**
     * @param raw raw value to convert to a string value
     * @return a new string value that wraps [raw]
     */
    @FromDynamicType(String::class)
    fun string(raw: Any) = Value(raw.toString())

    /**
     * @param raw raw value to convert to a number value
     * @return a new number value that wraps [raw]'s integer (if possible) or float value
     * @throws IllegalRawValueException if [raw] is not a valid numeric value
     */
    @FromDynamicType(Number::class)
    fun number(raw: Any): OutputValue<Number> =
        when (raw) {
            is Number -> Value(raw)
            else ->
                raw
                    .toString()
                    .let { it.toIntOrNull() ?: it.toFloatOrNull() }
                    ?.let { Value(it) }
                    ?: throw IllegalRawValueException("Not a numeric value", raw)
        }

    /**
     * @param raw raw value to convert to a boolean value.
     *            `true`,`yes` -> `true`,
     *            `false`,`no` -> `false`
     * @return a new boolean value that wraps [raw]'s boolean value, or `null` if [raw] does not represent a boolean
     * @throws IllegalRawValueException if [raw] is not a valid boolean value
     */
    @FromDynamicType(Boolean::class)
    fun boolean(raw: Any) =
        when (raw) {
            is Boolean -> Value(raw)
            else ->
                when (raw.toString().lowercase()) {
                    "true", "yes" -> Value(true)
                    "false", "no" -> Value(false)
                    else -> throw IllegalRawValueException("Not a valid boolean value", raw)
                }
        }

    /**
     * @param raw raw value to convert to a range value.
     *            The format is `x..y`, where `x` and `y` are integers that specify start and end of the range.
     *            Both start and end can be omitted to represent an open/infinite value on that end.
     * @return a new range value that wraps the parsed content of [raw]
     * @throws IllegalRawValueException if the value is an invalid range
     * @see iterable
     */
//    @FromDynamicType(Range::class)
//    fun range(raw: Any): ObjectValue<Range> {
//        if (raw is Range) return ObjectValue(raw)
//
//        val rawString = raw.toString()
//
//        // Matches 'x..y', where both x and y are optional integers.
//        val regex = "(\\d+)?..(\\d+)?".toRegex()
//
//        // If the raw value does not represent a range, an error is thrown.
//        if (!regex.matches(rawString)) {
//            throw IllegalRawValueException("Invalid range", raw)
//        }
//
//        val groups =
//            regex
//                .find(rawString)
//                ?.groupValues
//                ?.asSequence()
//                ?.iterator(consumeAmount = 1)
//
//        // Start of the range. If null (= not present), the range is open on the left end.
//        val start = groups?.next()
//        // End of the range. If null (= not present), the range is open on the right end.
//        val end = groups?.next()
//
//        // Indexes start from 1:
//        // 2..5 maps to Range(1, 4)
//        val range =
//            Range(
//                start?.toIntOrNull(),
//                end?.toIntOrNull(),
//            )
//
//        return ObjectValue(range)
//    }

    /**
     * @param raw raw value to convert to a size value.
     *            The format is `Xunit`, where `X` is a number (integer or floating point)
     *            and `unit` is one of the following: `px`, `pt`, `cm`, `mm`, `in`. If not specified, `px` is assumed.
     * @return a new size value that wraps the parsed content of [raw].
     * @throws IllegalRawValueException if the value is an invalid size
     */
//    @FromDynamicType(Size::class)
//    fun size(raw: Any): ObjectValue<Size> {
//        if (raw is Size) return ObjectValue(raw)
//
//        // All possible unit symbols.
//        val symbolsRegex = Size.Unit.entries.joinToString("|") { it.symbol }
//        // Matches value and unit, e.g. 10px, 12.5cm, -3in.
//        val regex = "^(-?\\d+(?:\\.\\d+)?)($symbolsRegex)?$".toRegex()
//        val groups =
//            regex
//                .find(raw.toString())
//                ?.groupValues
//                ?.asSequence()
//                ?.iterator(consumeAmount = 1)
//
//        // The value, which is required.
//        val value = groups?.next()?.toDoubleOrNull() ?: throw IllegalRawValueException("Invalid size", raw)
//
//        // The unit, which is optional and defaults to pixels.
//        val rawUnit = groups.next()
//        val unit = Size.Unit.entries.find { it.symbol.equals(rawUnit, ignoreCase = true) } ?: Size.Unit.PIXELS
//
//        return ObjectValue(Size(value, unit))
//    }

    /**
     * @param raw raw value to convert to a collection of sizes.
     * @see size for the treatment of each size
     * @throws IllegalRawValueException if the raw value contains a different amount of sizes than 1, 2 or 4,
     *                                  of if any of those values is an invalid size
     */
//    @FromDynamicType(Sizes::class)
//    fun sizes(raw: Any): Value<Sizes> {
//        if (raw is Sizes) return ObjectValue(raw)
//
//        val parts = raw.toString().split("\\s+".toRegex())
//        val iterator = parts.iterator()
//
//        return ObjectValue(
//            when (parts.size) {
//                // Single size: all sides are the same.
//                1 -> Sizes(all = size(iterator.next()).unwrappedValue)
//                // Two sizes: vertical and horizontal.
//                2 ->
//                    Sizes(
//                        vertical = size(iterator.next()).unwrappedValue,
//                        horizontal = size(iterator.next()).unwrappedValue,
//                    )
//                // Four sizes: top, right, bottom, left.
//                4 ->
//                    Sizes(
//                        top = size(iterator.next()).unwrappedValue,
//                        right = size(iterator.next()).unwrappedValue,
//                        bottom = size(iterator.next()).unwrappedValue,
//                        left = size(iterator.next()).unwrappedValue,
//                    )
//
//                else -> throw IllegalRawValueException("Invalid top-right-bottom-left sizes", raw)
//            },
//        )
//    }

    /**
     * @param raw raw value to convert to a color value, case-insensitive.
     *            Can be a hex value starting by `#` (e.g. `#FF0000`) or a color name (e.g. `red`).
     * @return a new color value that wraps the parsed content of [raw]
     * @throws IllegalRawValueException if the value is an invalid color
     */
//    @FromDynamicType(Color::class)
//    fun color(raw: Any): ObjectValue<Color> {
//        if (raw is Color) return ObjectValue(raw)
//
//        return Color.decode(raw.toString())?.let(::ObjectValue)
//            ?: throw IllegalRawValueException("Not a valid color", raw)
//    }

    /**
     * @param raw raw value to convert to an enum value
     * @param values enum values pool to pick the output value from
     * @return the value whose name matches (ignoring case and with `_`s removed) with [raw], or `null` if no match is found
     */
    @FromDynamicType(Enum::class)
    fun enum(
        raw: Any,
        values: Array<Enum<*>>,
    ) = when (raw) {
            is Enum<*> -> Value(raw)
            else ->
                values.find { it.name.equals(raw.toString(), ignoreCase = true) }
                    ?.let { Value(it) }
        }

}